﻿using System;
using System.Runtime.InteropServices;

namespace OmniKits.Windows.Scripting
{
    using EssIL;
    using Interop;
    using NullGuard;

    using EXCEPINFO = System.Runtime.InteropServices.ComTypes.EXCEPINFO;

    [ComImport]
    [Guid("BB1A2AE1-A4F9-11cf-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IActiveScript
    {
        void SetScriptSite(IActiveScriptSite site);
        [return: MarshalAs(UnmanagedType.IUnknown)]
        object GetScriptSite(ref Guid riid);
        void SetScriptState(ScriptState state);
        ScriptState GetScriptState();
        void Close();
        void AddNamedItem(string name, ScriptItemFlag flags);
        void AddTypeLib(Guid typeLib, uint major, uint minor, uint flags);
        [return: MarshalAs(UnmanagedType.IDispatch)]
        object GetScriptDispatch(string itemName);
        UIntPtr GetCurrentScriptThreadID();
        UIntPtr GetScriptThreadID(UIntPtr win32ThreadId);
        ScriptThreadState GetScriptThreadState(UIntPtr thread);
        [PreserveSig]
        HResult InterruptScriptThread(UIntPtr thread, out EXCEPINFO exceptionInfo, uint flags);
        IActiveScript Clone();
    }

    public enum ScriptProperty : uint
    {
        IntegerMode = 0x00003000,
        StringCompareInstance = 0x00003001,
        AbbreviateGlobalNameResolution = 0x70000002,
        InvokeVersioning = 0x00004000,
    }

    public enum ScriptLanguageVersion
    {
        Default = 0,
        JScript57 = 1,
        JScript58 = 2,

        CurrentValidMax = 15,
        Max = 255,
    }

    [ComImport]
    [Guid("4954E0D0-FBC7-11D1-8410-006008C3FBFC")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IActiveScriptProperty
    {
        object GetProperty(ScriptProperty property, UIntPtr index);
        void SetProperty(ScriptProperty property, UIntPtr index, ref object value);
    }

    [ComImport]
    [Guid("DB01A1E3-A42B-11cf-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IActiveScriptSite
    {
        int GetLCID();
        void GetItemInfo(
            string name, ScriptInfoFlag returnMask,
            [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.IUnknown)]
            object[] item,
            [Out, MarshalAs(UnmanagedType.LPArray)]
            IntPtr[] typeInfo);
        string GetDocVersionString();
        void OnScriptTerminate(object result, EXCEPINFO[] exceptionInfo);
        void OnStateChange(ScriptState scriptState);
        void OnScriptError(IActiveScriptError scriptError);
        void OnEnterScript();
        void OnLeaveScript();
    }

    [ComImport]
    [Guid("EAE1BA61-A4ED-11cf-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IActiveScriptError
    {
        //void GetExceptionInfo(out EXCEPINFO exceptionInfo);
        [return: MarshalAs(UnmanagedType.Struct)]
        EXCEPINFO GetExceptionInfo();
        void GetSourcePosition(out uint sourceContext, out uint lineNumber, out int characterPosition);
        string GetSourceLineText();
    }

    [ComImport]
    [Guid("BB1A2AE2-A4F9-11cf-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IActiveScriptParse32
    {
        void InitNew();

        [PreserveSig]
        HResult AddScriptlet(
            string defaultName, string code, string itemName,
            string subItemName, string eventName, string delimiter,
            IntPtr sourceContextCookie, uint startingLineNumber, ScriptTextFlag flags,
            out string name, out EXCEPINFO exceptionInfo);

        [PreserveSig]
        HResult ParseScriptText(
            string code, string itemName,
            [MarshalAs(UnmanagedType.IUnknown)]
            object context, string delimiter, IntPtr sourceContextCookie,
            uint startingLineNumber, ScriptTextFlag flags,
            [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct)]
            object[] result, out EXCEPINFO exceptionInfo);
    }

    [ComImport]
    [Guid("C7EF7658-E1EE-480E-97EA-D52CB4D76D17")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IActiveScriptParse64
    {
        void InitNew();

        [PreserveSig]
        HResult AddScriptlet(
            string defaultName, string code, string itemName,
            string subItemName, string eventName, string delimiter,
            IntPtr sourceContextCookie, uint startingLineNumber, ScriptTextFlag flags,
            out string name, out EXCEPINFO exceptionInfo);

        [PreserveSig]
        HResult ParseScriptText(
            string code, string itemName,
            [MarshalAs(UnmanagedType.IUnknown)]
            object context, string delimiter, IntPtr sourceContextCookie,
            uint startingLineNumber, ScriptTextFlag flags,
            [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct)]
            object[] result, out EXCEPINFO exceptionInfo);
    }

    public abstract class ActiveScriptParseWrapper
    {
        public static ActiveScriptParseWrapper New(object scriptEngine)
        {
            switch (IntPtr.Size)
            {
                case 4:
                    return new AspWrapper32(scriptEngine);
                case 8:
                    return new AspWrapper64(scriptEngine);
                default:
                    throw new PlatformNotSupportedException();
            }
        }

        public abstract void InitNew();
        public abstract HResult AddScriptlet(
            string defaultName, string code, string itemName,
            string subItemName, string eventName, string delimiter,
            IntPtr sourceContextCookie, uint startingLineNumber, ScriptTextFlag flags,
            out string name, out EXCEPINFO exceptionInfo);
        public abstract HResult ParseScriptText(
            string code, string itemName,
            object context, string delimiter, IntPtr sourceContextCookie,
            uint startingLineNumber, ScriptTextFlag flags,
            out object result, out EXCEPINFO exceptionInfo);

        [NullGuard(ValidationFlags.None)]
        private class AspWrapper32 : ActiveScriptParseWrapper
        {
            private readonly IActiveScriptParse32 _IAsp32;

            public AspWrapper32(object scriptEngine)
            {
                _IAsp32 = (IActiveScriptParse32)scriptEngine;
            }

            public override void InitNew()
                => _IAsp32.InitNew();

            public override HResult AddScriptlet(
                string defaultName, string code, string itemName,
                string subItemName, string eventName, string delimiter,
                IntPtr sourceContextCookie, uint startingLineNumber, ScriptTextFlag flags,
                out string name, out EXCEPINFO exceptionInfo)
            {
                return _IAsp32.AddScriptlet(
                    defaultName, code, itemName,
                    subItemName, eventName, delimiter,
                    sourceContextCookie, startingLineNumber, flags,
                    out name, out exceptionInfo);
            }

            public override HResult ParseScriptText(
                string code, string itemName,
                object context, string delimiter, IntPtr sourceContextCookie,
                uint startingLineNumber, ScriptTextFlag flags,
                out object result, out EXCEPINFO exceptionInfo)
            {
                var hasResult = flags.Has(ScriptTextFlag.IsExpression);
                var outResult = hasResult ? new object[1] : null;

                var hResult = _IAsp32.ParseScriptText(
                    code, itemName,
                    context, delimiter, sourceContextCookie,
                    startingLineNumber, flags,
                    outResult, out exceptionInfo);

                result = hasResult ? outResult[0] : null;
                return hResult;
            }
        }

        [NullGuard(ValidationFlags.None)]
        private class AspWrapper64 : ActiveScriptParseWrapper
        {
            private IActiveScriptParse64 _IAsp64;

            public AspWrapper64(object scriptEngine)
            {
                _IAsp64 = (IActiveScriptParse64)scriptEngine;
            }

            public override void InitNew()
                => _IAsp64.InitNew();

            public override HResult AddScriptlet(
                string defaultName, string code, string itemName,
                string subItemName, string eventName, string delimiter,
                IntPtr sourceContextCookie, uint startingLineNumber, ScriptTextFlag flags,
                out string name, out EXCEPINFO exceptionInfo)
            {
                return _IAsp64.AddScriptlet(
                    defaultName, code, itemName,
                    subItemName, eventName, delimiter,
                    sourceContextCookie, startingLineNumber, flags,
                    out name, out exceptionInfo);
            }

            public override HResult ParseScriptText(
                string code, string itemName,
                object context, string delimiter, IntPtr sourceContextCookie,
                uint startingLineNumber, ScriptTextFlag flags,
                out object result, out EXCEPINFO exceptionInfo)
            {
                var hasResult = flags.Has(ScriptTextFlag.IsExpression);
                var outResult = hasResult ? new object[1] : null;

                var hResult = _IAsp64.ParseScriptText(
                    code, itemName,
                    context, delimiter, sourceContextCookie,
                    startingLineNumber, flags,
                    outResult, out exceptionInfo);

                result = hasResult ? outResult[0] : null;
                return hResult;
            }
        }
    }
}
